﻿using Sidvall.Business;
using Sidvall.Data;
using Sidvall.IdentityManager.Data;
using System.Linq;
using System.Threading.Tasks;

namespace Sidvall.IdentityManager.Model.Local.DataPortalManagers
{
    public static partial class AspNetUsersManager
    {
        #region Partial Members

        #region OnBeforeExecute

        static partial void OnBeforeExecute(IDataPortalManager dataPortalManager, DataPortalContext<CommandCriteria, CommandCriteria> dataPortalContext)
        {
            dataPortalContext.Tasks.Add(OnBeforeExecuteTask(dataPortalManager, dataPortalContext));
        }
        private static async Task OnBeforeExecuteTask(IDataPortalManager dataPortalManager, DataPortalContext<CommandCriteria, CommandCriteria> dataPortalContext)
        {
            IAspNetUsers item;
            Sidvall.IdentityManager.Security.IUserTokenProvider userTokenProvider;
            Sidvall.Net.Mail.MailMessage mailMessage;
            Sidvall.IdentityManager.Security.PasswordVerificationResult result;
            string password, passwordHash, purpose, token, tokenProvider;

            var storagePortal = dataPortalManager.GenericDataPortal;
            switch (dataPortalContext.OperationContext.CriteriaId)
            {
                case ParameterManager.ConfirmPasswordCommandId:
                    item = await GetUserAsync(dataPortalManager, dataPortalContext, storagePortal, false).ConfigureAwait(false);
                    if ((item == null) || (string.IsNullOrWhiteSpace(item.Email)) || (item.EmailConfirmed))
                        throw new Sidvall.Security.UnauthorizedException("Invalid user");
                    item.EmailConfirmed = true;
                    await storagePortal.AspNetUsersManager.SaveItemAsync(item, Sidvall.Data.SaveMode.ForceUpdate).ConfigureAwait(false);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.CreateSecurityStampCommandId:
                    item = await GetUserAsync(dataPortalManager, dataPortalContext, storagePortal, false).ConfigureAwait(false);
                    await CreateSecurityStampAsync(storagePortal, item).ConfigureAwait(false);
                    dataPortalContext.Parameters.Add(ParameterManager.ResultParameterKey, item.SecurityStamp);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.CreateUserCommandId:
                    var user = NewUser(dataPortalContext);
                    item = await storagePortal.AspNetUsersManager.SaveItemAsync(user).ConfigureAwait(false);
                    user = Sidvall.IdentityManager.SystemContext.Current.EntityFacade.AspNetUsersManager.Clone(item);
                    dataPortalContext.Parameters.Add(ParameterManager.ResultParameterKey, Sidvall.Serialization.SerializerManager.Current.SerializeObject(user));
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.GenerateTokenCommandId:
                    item = await GetUserAsync(dataPortalManager, dataPortalContext, storagePortal, false).ConfigureAwait(false);
                    await CreateSecurityStampAsync(storagePortal, item).ConfigureAwait(false);
                    purpose = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.PurposeParameterName, string.Empty);
                    tokenProvider = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.TokenProviderParameterName, string.Empty);
                    userTokenProvider = Sidvall.IdentityManager.SystemContext.Current.GetUserTokenProvider(tokenProvider);
                    token = await userTokenProvider.GenerateAsync(purpose, item).ConfigureAwait(false);
                    dataPortalContext.Parameters.Add(ParameterManager.ResultParameterKey, token);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.ResetPasswordCommandId:
                    item = await GetUserAsync(dataPortalManager, dataPortalContext, storagePortal, false).ConfigureAwait(false);
                    if ((item == null) || (string.IsNullOrWhiteSpace(item.Email)) || (!item.EmailConfirmed))
                        throw new Sidvall.Security.UnauthorizedException("Invalid user");
                    password = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.PasswordParameterName, string.Empty);
                    item.PasswordHash = Sidvall.IdentityManager.SystemContext.Current.PasswordHasher.HashPassword(password);
                    await storagePortal.AspNetUsersManager.SaveItemAsync(item, Sidvall.Data.SaveMode.ForceUpdate).ConfigureAwait(false);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.SendConfirmEmailCommandId:
                    item = await GetUserAsync(dataPortalManager, dataPortalContext, storagePortal, false).ConfigureAwait(false);
                    if ((item == null) || (string.IsNullOrWhiteSpace(item.Email)) || (item.EmailConfirmed))
                        throw new Sidvall.Security.UnauthorizedException("Invalid user");
                    await CreateSecurityStampAsync(storagePortal, item).ConfigureAwait(false);
                    userTokenProvider = Sidvall.IdentityManager.SystemContext.Current.GetUserTokenProvider();
                    token = await userTokenProvider.GenerateAsync(Sidvall.IdentityManager.Security.SecurityManager.ConfirmEmailTokenPurpose, item).ConfigureAwait(false);
                    mailMessage = await Sidvall.IdentityManager.SystemContext.Current.GetMailMessageAsync(Net.Mail.MailType.ConfirmationEmail, item, token).ConfigureAwait(false);
                    await Sidvall.SystemContext.Current.SmtpClientManager.SendAsync(mailMessage).ConfigureAwait(false);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.SendResetPasswordEmailCommandId:
                    item = await GetUserAsync(dataPortalManager, dataPortalContext, storagePortal, false).ConfigureAwait(false);
                    if ((item == null) || (string.IsNullOrWhiteSpace(item.Email)) || (!item.EmailConfirmed))
                        throw new Sidvall.Security.UnauthorizedException("Invalid user");
                    await CreateSecurityStampAsync(storagePortal, item).ConfigureAwait(false);
                    userTokenProvider = Sidvall.IdentityManager.SystemContext.Current.GetUserTokenProvider();
                    token = await userTokenProvider.GenerateAsync(Sidvall.IdentityManager.Security.SecurityManager.ResetPasswordTokenPurpose, item).ConfigureAwait(false);
                    mailMessage = await Sidvall.IdentityManager.SystemContext.Current.GetMailMessageAsync(Net.Mail.MailType.ResetPasswordEmail, item, token).ConfigureAwait(false);
                    await Sidvall.SystemContext.Current.SmtpClientManager.SendAsync(mailMessage).ConfigureAwait(false);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.VerifyPasswordCommandId:
                    passwordHash = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.PasswordHashParameterName, string.Empty);
                    password = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.PasswordParameterName, string.Empty);
                    result = Sidvall.IdentityManager.SystemContext.Current.PasswordHasher.VerifyHashedPassword(passwordHash, password);
                    dataPortalContext.OperationContext.Parameters.Add(ParameterManager.ResultParameterKey, (int)result);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.VerifyUserPasswordCommandId:
                    item = await GetUserAsync(dataPortalManager, dataPortalContext, storagePortal, false).ConfigureAwait(false);
                    if (item == null)
                        throw new Sidvall.Security.UnauthorizedException("Invalid user");
                    password = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.PasswordParameterName, string.Empty);
                    result = Sidvall.IdentityManager.SystemContext.Current.PasswordHasher.VerifyHashedPassword(item.PasswordHash, password);
                    dataPortalContext.OperationContext.Parameters.Add(ParameterManager.ResultParameterKey, (int)result);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                case ParameterManager.VerifyTokenCommandId:
                    item = await GetUserAsync(dataPortalManager, dataPortalContext, storagePortal, false).ConfigureAwait(false);
                    if (item == null)
                    {
                        dataPortalContext.Parameters.Add(ParameterManager.ResultParameterKey, false);
                        return;
                    }
                    purpose = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.PurposeParameterName, string.Empty);
                    token = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.TokenParameterName, string.Empty);
                    tokenProvider = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.TokenProviderParameterName, string.Empty);
                    userTokenProvider = Sidvall.IdentityManager.SystemContext.Current.GetUserTokenProvider(tokenProvider);
                    var isValid = await userTokenProvider.ValidateAsync(purpose, token, item).ConfigureAwait(false);
                    if (isValid)
                        dataPortalContext.Parameters.Add(ParameterManager.ResultParameterKey, true);
                    else
                        dataPortalContext.Parameters.Add(ParameterManager.ResultParameterKey, false);
                    dataPortalContext.Result = Sidvall.Business.DataPortalResult.Ok;
                    break;
                default:
                    break;
            }
        }

        #endregion

        #endregion

        #region Private Members

        #region CreateSecurityStampAsync

        private static async Task CreateSecurityStampAsync(GenericDataPortal dataPortal, IAspNetUsers item)
        {
            if (item == null)
                throw new Sidvall.Security.UnauthorizedException("Invalid user");
            item.SecurityStamp = Sidvall.IdentityManager.Security.SecurityManager.CreateSecurityStamp();
            await dataPortal.AspNetUsersManager.SaveItemAsync(item, Sidvall.Data.SaveMode.ForceUpdate).ConfigureAwait(false);
        }

        #endregion
        #region GetUserAsync

        private static async Task<IAspNetUsers> GetUserAsync(IDataPortalManager dataPortalManager,
            Sidvall.Business.DataPortalContext<Sidvall.Data.CommandCriteria, Sidvall.Data.CommandCriteria> dataPortalContext,
            GenericDataPortal dataPortal, bool checkSecurityStamp)
        {
            IAspNetUsers item;
            string userId, userName, email, securityStamp;

            if (checkSecurityStamp)
            {
                securityStamp = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.SecurityStampParameterName, string.Empty);
                if (string.IsNullOrWhiteSpace(securityStamp))
                    return null;
            }
            else
                securityStamp = null;
            item = null;
            if (item == null)
            {
                userId = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.UserIdParameterName, string.Empty);
                if (!string.IsNullOrWhiteSpace(userId))
                {
                    item = await dataPortal.AspNetUsersManager.GetItemByPrimaryKeyAsync(userId).ConfigureAwait(false);
                }
            }
            if (item == null)
            {
                userName = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.UserNameParameterName, string.Empty);
                if (!string.IsNullOrWhiteSpace(userName))
                {
                    item = await dataPortal.AspNetUsersManager.GetItemByUserNameAsync(userName).ConfigureAwait(false);
                }
            }
            if (item == null)
            {
                email = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.EmailParameterName, string.Empty);
                if (!string.IsNullOrWhiteSpace(email))
                {
                    item = (await dataPortal.AspNetUsersManager.GetItemsByEmailAsync(email).ConfigureAwait(false)).DataItems.FirstOrDefault();
                }
            }
            if (item != null)
            {
                if ((!string.IsNullOrWhiteSpace(securityStamp)) && (item.SecurityStamp != securityStamp))
                    return null;
                item = dataPortalManager.GenericEntityFacade.AspNetUsersManager.Copy(item, Sidvall.Data.CopySettings.FullCopyCurrent(Sidvall.Data.DataRowContextOption.None, Sidvall.Data.DataRowState.Unchanged));
                return item;
            }
            else
                return null;
        }

        #endregion
        #region NewUser

        private static Sidvall.IdentityManager.Net.Services.V1.AspNetUsers NewUser(Sidvall.Business.DataPortalContext<Sidvall.Data.CommandCriteria, Sidvall.Data.CommandCriteria> dataPortalContext)
        {
            var item = new Sidvall.IdentityManager.Net.Services.V1.AspNetUsers()
            {
                Email = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.EmailParameterName, string.Empty),
                EmailConfirmed = dataPortalContext.OperationContext.Parameters.ValueToBoolean(ParameterManager.EmailConfirmedParameterName, false),
                PhoneNumber = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.PhoneNumberParameterName, string.Empty),
                PhoneNumberConfirmed = dataPortalContext.OperationContext.Parameters.ValueToBoolean(ParameterManager.PhoneNumberConfirmedParameterName, false),
                AspNetUserClaims = new Sidvall.IdentityManager.Net.Services.V1.AspNetUserClaimsCollectionItem(),
            };
            item.UserName = item.Email;
            var name = dataPortalContext.OperationContext.Parameters.ValueToString(ParameterManager.NameParameterName, string.Empty);
            if (!string.IsNullOrWhiteSpace(name))
            {
                var claim = new Sidvall.IdentityManager.Net.Services.V1.AspNetUserClaims()
                {
                    ClaimType = Sidvall.Security.ClaimTypes.Name,
                    ClaimValue = name,
                };
                item.AspNetUserClaims.Add(claim);
            }
            return item;
        }

        #endregion

        #endregion
    }
}